﻿using System;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace Xuzzle
{
    public class XuzzlePage : ContentPage
    {
        // Number of squares horizontally and vertically,
        //  but if you change it, some code will break.
        static readonly int NUM = 4;

        // Array of XuzzleSquare views, and empty row & column.
        XuzzleSquare[,] squares = new XuzzleSquare[NUM, NUM];
        int emptyRow = NUM - 1;
        int emptyCol = NUM - 1;

        StackLayout stackLayout;
        AbsoluteLayout absoluteLayout;
        Button randomizeButton;
        Label timeLabel;
        double squareSize;
        bool isBusy;
        bool isPlaying;

        public XuzzlePage()
        {
            // AbsoluteLayout to host the squares.
            absoluteLayout = new AbsoluteLayout()
            {
                HorizontalOptions = LayoutOptions.Center,
                VerticalOptions = LayoutOptions.Center
            };

            // Create XuzzleSquare's for all the rows and columns.
            string text = "{XAMARIN.FORMS}";
            string winText = "CONGRATULATIONS";
            int index = 0;

            for (int row = 0; row < NUM; row++)
            {
                for (int col = 0; col < NUM; col++)
                {
                    // But skip the last one!
                    if (row == NUM - 1 && col == NUM - 1)
                        break;

                    // Instantiate XuzzleSquare.
                    XuzzleSquare square = new XuzzleSquare(text[index], winText[index], index)
                    {
                        Row = row,
                        Col = col
                    };

                    // Add tap recognition
                    TapGestureRecognizer tapGestureRecognizer = new TapGestureRecognizer
                    {
                        Command = new Command(OnSquareTapped),
                        CommandParameter = square
                    };
                    square.GestureRecognizers.Add(tapGestureRecognizer);

                    // Add it to the array and the AbsoluteLayout.
                    squares[row, col] = square;
                    absoluteLayout.Children.Add(square);
                    index++;
                }
            }

            // This is the "Randomize" button.
            randomizeButton = new Button
            {
                Text = "Randomize",
                HorizontalOptions = LayoutOptions.Center,
                VerticalOptions = LayoutOptions.CenterAndExpand
            };
            randomizeButton.Clicked += OnRandomizeButtonClicked;

            // Label to display elapsed time.
            timeLabel = new Label
            {
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                FontAttributes = FontAttributes.Bold,
                HorizontalOptions = LayoutOptions.Center,
                VerticalOptions = LayoutOptions.CenterAndExpand
            };

            // Put everything in a StackLayout.
            stackLayout = new StackLayout
            {
                Children = {
                    new StackLayout {
                        VerticalOptions = LayoutOptions.FillAndExpand,
                        HorizontalOptions = LayoutOptions.FillAndExpand,
                        Children = {
                            randomizeButton,
                            timeLabel
                        }
                    },
                    absoluteLayout
                }
            };
            stackLayout.SizeChanged += OnStackSizeChanged;

            // And set that to the content of the page.
            this.Padding = new Thickness(0, Device.RuntimePlatform == Device.iOS ? 20 : 0, 0, 0);
            this.Content = stackLayout;
        }

        void OnStackSizeChanged(object sender, EventArgs args)
        {
            double width = stackLayout.Width;
            double height = stackLayout.Height;

            if (width <= 0 || height <= 0)
                return;

            // Orient StackLayout based on portrait/landscape mode.
            stackLayout.Orientation = (width < height) ? StackOrientation.Vertical :
                                                         StackOrientation.Horizontal;

            // Calculate square size and position based on stack size.
            squareSize = Math.Min(width, height) / NUM;
            absoluteLayout.WidthRequest = NUM * squareSize;
            absoluteLayout.HeightRequest = NUM * squareSize;

            foreach (View view in absoluteLayout.Children)
            {
                XuzzleSquare square = (XuzzleSquare)view;
                square.SetLabelFont(0.4 * squareSize, FontAttributes.Bold);

                AbsoluteLayout.SetLayoutBounds(square,
                    new Rectangle(square.Col * squareSize,
                        square.Row * squareSize,
                        squareSize,
                        squareSize));
            }
        }

        async void OnSquareTapped(object parameter)
        {
            if (isBusy)
                return;

            isBusy = true;
            XuzzleSquare tappedSquare = (XuzzleSquare)parameter;
            await ShiftIntoEmpty(tappedSquare.Row, tappedSquare.Col);
            isBusy = false;

            // Check for a "win".
            if (isPlaying)
            {
                int index;

                for (index = 0; index < NUM * NUM - 1; index++)
                {
                    int row = index / NUM;
                    int col = index % NUM;
                    XuzzleSquare square = squares[row, col];
                    if (square == null || square.Index != index)
                        break;
                }

                // We have a winner!
                if (index == NUM * NUM - 1)
                {
                    isPlaying = false;
                    await DoWinAnimation();
                }
            }
        }

        async Task ShiftIntoEmpty(int tappedRow, int tappedCol, uint length = 100)
        {
            // Shift columns.
            if (tappedRow == emptyRow && tappedCol != emptyCol)
            {
                int inc = Math.Sign(tappedCol - emptyCol);
                int begCol = emptyCol + inc;
                int endCol = tappedCol + inc;

                for (int col = begCol; col != endCol; col += inc)
                {
                    await AnimateSquare(emptyRow, col, emptyRow, emptyCol, length);
                }
            }
            // Shift rows.
            else if (tappedCol == emptyCol && tappedRow != emptyRow)
            {
                int inc = Math.Sign(tappedRow - emptyRow);
                int begRow = emptyRow + inc;
                int endRow = tappedRow + inc;

                for (int row = begRow; row != endRow; row += inc)
                {
                    await AnimateSquare(row, emptyCol, emptyRow, emptyCol, length);
                }
            }
        }

        async Task AnimateSquare(int row, int col, int newRow, int newCol, uint length)
        {
            // The Square to be animated.
            XuzzleSquare animaSquare = squares[row, col];

            // The destination rectangle.
            Rectangle rect = new Rectangle(squareSize * emptyCol,
                                          squareSize * emptyRow,
                                          squareSize,
                                          squareSize);

            // This is the actual animation call.
            await animaSquare.LayoutTo(rect, length);

            // Set several variables and properties for new layout.
            squares[newRow, newCol] = animaSquare;
            animaSquare.Row = newRow;
            animaSquare.Col = newCol;
            squares[row, col] = null;
            emptyRow = row;
            emptyCol = col;
        }

        async void OnRandomizeButtonClicked(object sender, EventArgs args)
        {
            Button button = (Button)sender;
            button.IsEnabled = false;
            Random rand = new Random();

            isBusy = true;

            // Simulate some fast crazy taps.
            for (int i = 0; i < 100; i++)
            {
                await ShiftIntoEmpty(rand.Next(NUM), emptyCol, 25);
                await ShiftIntoEmpty(emptyRow, rand.Next(NUM), 25);
            }
            button.IsEnabled = true;

            isBusy = false;

            // Prepare for playing.
            DateTime startTime = DateTime.Now;

            Device.StartTimer(TimeSpan.FromSeconds(1), () => {
                // Round duration and get rid of milliseconds.
                TimeSpan timeSpan = (DateTime.Now - startTime) +
                                                    TimeSpan.FromSeconds(0.5);
                timeSpan = new TimeSpan(timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);

                // Display the duration.
                if (isPlaying)
                    timeLabel.Text = timeSpan.ToString("t");
                return isPlaying;
            });
            this.isPlaying = true;
        }

        async Task DoWinAnimation()
        {
            // Inhibit all input.
            randomizeButton.IsEnabled = false;
            isBusy = true;

            for (int cycle = 0; cycle < 2; cycle++)
            {
                foreach (XuzzleSquare square in squares)
                    if (square != null)
                        await square.AnimateWinAsync(cycle == 1);

                if (cycle == 0)
                    await Task.Delay(1500);
            }

            // All input.
            randomizeButton.IsEnabled = true;
            isBusy = false;
        }
    }
}
